package mysql

import (
	"crypto/md5"
	"database/sql"
	"encoding/hex"
	"struggle/model"
	"struggle/pkg/snowflake"

	"github.com/jmoiron/sqlx"
	"go.uber.org/zap"
)

// 存放数据库相关操作，增删改查
var secret = "用户密码加密的盐"

// encryptPassword 加密
func encryptPassword(data []byte) (result string) {
	h := md5.New()
	h.Write([]byte(secret))                // 先加盐
	return hex.EncodeToString(h.Sum(data)) // 再md5并转为16进制字符串
}

// Register 将指定用户注册
func Register(user *model.User) (err error) {
	// 涉及处理多表，需要使用事务
	tx, err := db.Begin()
	if err != nil {
		zap.L().Error("mysql transaction failed", zap.Error(err))
		return
	}
	defer ClearTransaction(tx) // 如果出现异常情况,导致没有 commit和rollback,可以用来收尾

	// 1. 先判断当前用户是否已经被注册过
	if CheckUserIsExist(user.Email) {
		return ErrorUserExit
	}
	// 2. 明文密码要加盐加密处理才能入库
	password := encryptPassword([]byte(user.Password))
	userID, err := snowflake.GenID()
	if err != nil {
		zap.L().Error("snowflake.GenID failed", zap.Error(err))
		return err
	}
	// 3. 入库
	sqlStr := `INSERT INTO st_user (user_sn, email, store_passwd) values (?, ?, ?)`
	_, err = tx.Exec(sqlStr, userID, user.Email, password)
	if err != nil {
		return
	}

	// 4. 新增用户统计表
	userCount := &model.UserCount{
		UID: userID,
	}
	if err = DefaultUserCount.Create(userCount); err != nil {
		return
	}

	// 5. 事务提交
	if err := tx.Commit(); err != nil {
		zap.L().Error("commit view failed", zap.Any("user", user), zap.Error(err))
	}
	return
}

func CheckUserIsExist(email string) bool {
	sqlStr := "SELECT COUNT(uid) FROM st_user WHERE email = ?"
	var count int64
	err := db.Get(&count, sqlStr, email)
	if err != nil && err != sql.ErrNoRows {
		zap.L().Error("query user exist failed", zap.Error(err))
		return true
	}
	return count > 0
}

func Login(user *model.User) (err error) {
	originPassword := user.Password // 记录一下原始密码
	sqlStr := `SELECT user_sn, store_passwd from st_user where email = ?`
	err = db.Get(user, sqlStr, user.Email)
	if err != nil && err != sql.ErrNoRows {
		// 查询数据库出错
		zap.L().Error("查询数据库出错", zap.Error(err))
		return
	}
	if err == sql.ErrNoRows {
		// 用户不存在
		return ErrorUserNotExit
	}
	// 生成加密密码与查询到的密码比较
	password := encryptPassword([]byte(originPassword))
	if string(user.StorePasswd) != password {
		return ErrorPasswordWrong
	}
	return
}

func GetUserInfoList(idList []uint64) (userList []*model.User, err error) {
	userList = make([]*model.User, 0, len(idList))
	sqlStr := `SELECT user_sn, email from st_user where user_sn in (?)`
	inSql, args, err := sqlx.In(sqlStr, idList)
	if err != nil {
		zap.L().Error("build sqlx.In failed", zap.Error(err))
		return
	}
	queryStr := db.Rebind(inSql)
	err = db.Select(&userList, queryStr, args...)
	if err != nil {
		zap.L().Error("db.Select failed", zap.Error(err))
		return
	}
	return
}

func GetUserByID(idStr string) (user *model.User, err error) {
	user = new(model.User)
	sqlStr := `SELECT user_sn, email, nickname, introduce, avatar, gender, state, is_root, created_at from st_user where user_sn = ?`
	err = db.Get(user, sqlStr, idStr)
	return
}

// increaseArticle 增加一篇文章，更新 user_count 表中的文章数和字数
func IncreaseArticle(uid uint64, num int) (err error) {
	// 插入一条数据，存在就更新，不存在就插入（必须现有唯一键） 以下两种方式相同
	sqlStr := `UPDATE st_user_count SET article_num=article_num+1, words=words+? WHERE uid=?`
	_, err = db.Exec(sqlStr, num, uid)
	//sqlStr := `INSERT INTO st_user_count (uid, article_num, words) values (?, 1, ?) ON DUPLICATE KEY UPDATE article_num=article_num+1, words=words+?`
	//_, err = db.Exec(sqlStr, uid, num, num)
	if err != nil {
		zap.L().Error("增加一篇文章，更新 user_count 表中的文章数和字数", zap.Error(err))
		return
	}
	return
}

// 更新用户文件表
func IncreaseFileMeta(fileMeta *model.FileMeta) (err error) {
	sqlStr := `INSERT IGNORE INTO st_user_file (user_sn, file_sha1, file_size, file_name, upload_at, status) VALUES (?,?,?,?,?,1)`
	_, err = db.Exec(sqlStr, fileMeta.UserSn, fileMeta.FileSha1, fileMeta.FileSize, fileMeta.FileName, fileMeta.UploadAt)
	if err != nil {
		zap.L().Error("创建用户文件表出错", zap.Error(err))
		return
	}
	return
}
